import type {
  Request,
  Result,
  RequestConfig,
  RequestOptions,
  RequestFileConfig,
  RequestFilesConfig,
  Progress,
  Files,
  StreamRequestOptions,
  AbortablePromise,
} from './types';

import { cloneDeep } from 'lodash-es';

import { useEnv } from '@/hooks/useEnv';
import { isArray, isObject } from 'lodash-es';

import { RequestTransform } from './index';
import { CacheEnum } from '@/enums/CacheEnum';
import { Toast, isReqUrl } from '..';

const { getEnv } = useEnv();

/**
 * 当前是否有网络连接
 */
let isConnected = true;

// 获取网络类型
uni.getNetworkType({
  success: (res) => {
    isConnected = res.networkType !== 'none';
  },
});

// 监听网络状态变化
uni.onNetworkStatusChange((res) => {
  isConnected = res.networkType !== 'none';
});

export abstract class request {
  public config: RequestConfig;
  public options: RequestOptions;
  public result: Request<Result> | undefined;
  public error: any;
  public requestTask: UniApp.RequestTask | UniApp.DownloadTask | UniApp.UploadTask | undefined; // requestTask 对象

  /** 超时loading 控制 */
  private timeOutLoading: NodeJS.Timeout | null = null;
  private timeOutLoadingNum = 0;
  private timeOutIsExec = false;

  constructor({ config, options }: { config: RequestConfig; options?: RequestOptions }) {
    config.header = Object.assign(this.getDefaultHeader(), config.header || {});
    this.config = config;
    this.options = options || {};
  }

  async beforeRequestHook() { }
  async requestHook(): Promise<any> | never { }
  requestCatchHook() { }
  resolve(un: any) { }
  reject(err: any) { }

  private requestComplete() {
    if (!this.options.disableLoading) {
      this.clearTimeOutLoading()
    }
  }

  /**
   * 接口列表执行超过1500 显示loading
   * @memberof request
   */
  setTimeOutLoading(title: string = '加载中...') {
    if (this.timeOutLoading) return;
    this.timeOutLoading = setTimeout(() => {
      this.timeOutIsExec = true;
      uni.showLoading({ title, mask: true });
    }, 1500);
  }

  /**
   * 接口队列执行完成后 清除定时器并隐藏loading
   * @memberof request
   */
  clearTimeOutLoading() {
    if (this.timeOutLoading) {
      if (this.timeOutIsExec) {
        uni.hideLoading();
      }
      clearTimeout(this.timeOutLoading);
      this.timeOutLoading = null;
    }
  }

  /**
   * 请求地址
   */
  getUrl(url: string = ''): string {
    // #ifdef MP
    return url && isReqUrl(url) ? url : getEnv('BASE_URL') + url;
    // #endif
    // #ifdef H5
    return window.location.origin + '/api/' + url;
    // #endif
  }

  /**
   * 默认header
   */
  getDefaultHeader() {
    return {
      token: uni.getStorageSync(CacheEnum.TOKEN),
      appId: uni.getStorageSync(CacheEnum.APPID),
      tenantId: 888
    };
  }

  // 显示网络不可用
  showNetworkUnavailable() {
    if (isConnected) return false;
    const msg = '当前网络不可用，请连接网络!';
    Toast(msg);
    return msg;
  }

  /**
   * 请求之前处理数据
   * @returns
   */
  private async createData() {
    const networkErr = this.showNetworkUnavailable();
    if (networkErr) {
      this.Error(networkErr);
      return true;
    }

    const { data } = this.config;
    if (data && isObject(data)) {
      let d = data as { [x: string]: any };
      Object.keys(d).forEach((k) => d[k] === undefined && delete (this.config.data as typeof d)[k]);
    }
    try {
      await this.beforeRequestHook();
    } catch (err) {
      this.Error(err);
      return true;
    }
  }

  /**
   * 收到开发者服务器成功返回的回调函数
   * @param result
   * @returns
   */
  private async Success(result: any) {
    this.result = result;
    try {
      const res = await this.requestHook();
      return this.resolve(res);
    } catch (err) {
      this.error = err;
      this.requestCatchHook();
      return this.reject(err);
    }
  }

  /**
   * 	接口调用失败的回调函数
   * @param err
   * @returns
   */
  private async Error(err: any) {
    this.error = err;
    this.requestCatchHook();
    return this.reject(err);
  }

  // 下载文件
  download(): Promise<string> {
    return new Promise(async (resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;

      if (!this.options.disableLoading) {
        this.setTimeOutLoading('下载中...');
      }

      const parame = this.config as RequestFileConfig;

      this.requestTask = uni.downloadFile({
        url: parame.url as string,
        header: parame.header,
        success: ({ tempFilePath, statusCode }) => {
          this.Success({
            statusCode,
            data: {
              code: 0,
              msg: '操作成功',
              data: tempFilePath,
            },
          });
        },
        fail: (err) => this.Error(err),
        complete: () => this.requestComplete(),
      });
    });
  }

  // 发起网络请求
  req<T = any>(): Promise<T> {
    return new Promise(async (resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;

      const err = await this.createData();
      if (err === true) return;

      if (!this.options.disableLoading) {
        this.setTimeOutLoading();
      }

      this.requestTask = uni.request({
        ...{
          ...this.config,
          url: this.getUrl(this.config?.url),
        },
        success: (res) => this.Success(res),
        fail: (err) => this.Error(err),
        complete: () => this.requestComplete(),
      });
    });
  }

  // 文件上传
  file<T = any>(): Promise<T> {
    return new Promise(async (resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;

      const err = await this.createData();
      if (err === true) return;

      if (!this.options.disableLoading) {
        this.setTimeOutLoading('上传中...');
      }

      const parame = this.config as RequestFileConfig;
      this.requestTask = uni.uploadFile({
        url: this.getUrl(parame?.url),
        filePath: parame.files.path,
        name: parame.name || 'file',
        formData: parame.formData,
        header: parame.header,
        timeout: parame.timeout,
        success: (res) => this.Success(res),
        fail: (err) => this.Error(err),
        complete: () => this.requestComplete(),
      });

      (this.requestTask as UniApp.UploadTask)?.onProgressUpdate(
        this.options?.onProgressUpdate ?? (() => { }),
      );
    });
  }

  // 流式请求方法
  stream<T = any>(): AbortablePromise<T> {
    const promise = new Promise<T>(async (resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;

      const err = await this.createData();
      if (err === true) return;
      // 设置请求头，接受事件流
      const header = {
        ...this.config.header,
        'Content-Type': 'application/json'
      };
      const streamOptions = this.options as StreamRequestOptions;

      this.requestTask = wx.request({
        ...{
          ...this.config,
          url: this.getUrl(this.config?.url),
          header,
          enableChunked: true, // 启用分块传输
        },
        fail: (err) => {
          console.log('流式请求失败', err);
          if (streamOptions.onError) {
            streamOptions.onError(err);
          }
        },
        complete: () => {
          if (streamOptions.onComplete) {
            console.log('流式请求完成');
            streamOptions.onComplete();
          }
        },
      });

      // 处理分块数据
      const task = this.requestTask as any;
      if (task.onChunkReceived) {
        task.onChunkReceived((res: { data: Uint8Array }) => {

          try {
            const uint8Array = new Uint8Array(res.data);
            // console.log('uint8Array', uint8Array);
            const decStr = decodeURIComponent(escape(String.fromCharCode.apply(null, Array.from(uint8Array))));
            // console.log('原始数据', decStr);
            const chunk = decStr.replace(/data:/g, '').replace(/\n/g, '').replace(/<br>/g, '\n');
            console.log(chunk);
            // 调用回调函数
            if (streamOptions.onChunkReceived && chunk) {
              streamOptions.onChunkReceived(chunk);
            }
          } catch (e) {
            console.error('处理数据块时出错:', e);
          }
        });
      } else {
        console.warn('当前环境不支持流式接收');
      }

      // 修改取消处理
      promise.abort = () => {
        if (this.requestTask) {
          this.requestTask.abort();
          // 调用取消回调而不是错误回调
          if (streamOptions.onCancel) {
            streamOptions.onCancel();
          }
        }
      };
    }) as AbortablePromise<T>;

    return promise;
  }

}

export function createRequest() {
  const files: Files = <T = any>(
    config: RequestFileConfig | RequestFilesConfig,
    options: any,
  ): Promise<T | T[]> => {
    const files = config.files;
    if (!isArray(files))
      return new RequestTransform({
        config: config as RequestConfig,
        options,
      }).file<T>();
    const progress = new Map<number, Progress>();
    const uploads: Promise<T>[] = files.map((files, k) => {
      progress.set(k, {
        progress: 0,
        totalBytesSent: 0,
        totalBytesExpectedToSend: 0,
      });
      return new RequestTransform({
        config: { ...config, files } as RequestConfig,
        options: {
          ...options,
          onProgressUpdate(res) {
            progress.set(k, cloneDeep(res));
            options?.onProgressUpdate(progress);
          },
        },
      }).file<T>();
    });
    return Promise.all(uploads);
  };

  return {
    get: <T = any>(config: RequestConfig, options?: RequestOptions): Promise<T> =>
      new RequestTransform({
        config: { ...config, method: 'GET' },
        options,
      }).req<T>(),
    post: <T = any>(config: RequestConfig, options?: RequestOptions): Promise<T> =>
      new RequestTransform({
        config: { ...config, method: 'POST' },
        options,
      }).req<T>(),
    put: <T = any>(config: RequestConfig, options?: RequestOptions): Promise<T> =>
      new RequestTransform({
        config: { ...config, method: 'PUT' },
        options,
      }).req<T>(),
    delete: <T = any>(config: RequestConfig, options?: RequestOptions): Promise<T> =>
      new RequestTransform({
        config: { ...config, method: 'DELETE' },
        options,
      }).req<T>(),
    files,
    stream: <T = any>(config: RequestConfig, options?: StreamRequestOptions): AbortablePromise<T> =>
      new RequestTransform({
        config: { ...config, method: config.method || 'POST' },
        options,
      }).stream<T>(),
    download: (url: RequestConfig['url'], options?: RequestOptions) =>
      new RequestTransform({ config: { url: url }, options }).download(),
  };
}
